package com.google.code.p.restrpc;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * @author dz
 */
public class DefaultServiceNode implements ServiceNode {
    
    private static final Log logger = LogFactory.getLog(DefaultServiceNode.class);
    
    public static ServiceNode[] parse(String url) {
        if (StringUtils.isEmpty(url)) {
            throw new IllegalArgumentException("url can't be null or empty!");
        }
        List<ServiceNode> nodes = new ArrayList<ServiceNode>();
        DefaultServiceNode node = new DefaultServiceNode();
        nodes.add(node);
        
        char[] chars = url.toCharArray();
        
        boolean colonFound = false, commaFound = false;
        int urlPart = 0; // {0:address,1:port}
        
        StringBuilder addressSb = new StringBuilder();
        StringBuilder portSb = new StringBuilder();
        
        for (int i = 0, length = chars.length; i < length; i++) {
            char c = chars[i];
            switch (c) {
                case ':':
                    if (urlPart < 1) {
                        urlPart = 1;
                    }
                    colonFound = true;
                    break;
                
                case ',':
                    if (urlPart < 2) {
                        node.setAddress(addressSb.toString());
                        try {
                            node.setPort(Integer.parseInt(portSb.toString()));
                        }
                        catch (NumberFormatException e) {
                            // TODO Auto-generated catch block
                            e.printStackTrace();
                        }
                        
                        node = new DefaultServiceNode();
                        nodes.add(node);
                        
                        addressSb = new StringBuilder();
                        portSb = new StringBuilder();
                        
                        urlPart = 0;
                    }
                    commaFound = true;
                    break;
                
                default:
                    switch (urlPart) {
                        case 0:
                            addressSb.append(c);
                            break;
                        case 1:
                            portSb.append(c);
                            break;
                    }
            }
        }
        
        node.setAddress(addressSb.toString());
        try {
            node.setPort(Integer.parseInt(portSb.toString()));
        }
        catch (NumberFormatException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        
        return nodes.toArray(new ServiceNode[] {});
    }
    
    private String address;
    
    private int port = 80;
    
    private String contextPath;
    
    private AtomicBoolean available = new AtomicBoolean(true);
    
    private int weight = 1;
    
    private long lastTimeMillis = -1;
    
    public DefaultServiceNode() {
    }
    
    public DefaultServiceNode(String address,
                              String contextPath,
                              boolean available,
                              int weight) {
        super();
        this.address = address;
        this.contextPath = contextPath;
        this.setAvailable(available);
        this.setWeight(weight);
    }
    
    public DefaultServiceNode(String address,
                              int port,
                              String contextPath,
                              boolean available,
                              int weight) {
        super();
        this.address = address;
        this.port = port;
        this.contextPath = contextPath;
        this.setAvailable(available);
        this.setWeight(weight);
    }
    
    public String getUrl() {
        if (port == 80) {
            return address;
        }
        return new StringBuilder(address).append(':').append(port).toString();
    }
    
    public String getAddress() {
        return address;
    }
    
    public void setAddress(String address) {
        this.address = address;
    }
    
    public int getPort() {
        return port;
    }
    
    public void setPort(int port) {
        this.port = port;
    }
    
    public String getContextPath() {
        return contextPath;
    }
    
    public void setContextPath(String contextPath) {
        this.contextPath = contextPath;
    }
    
    public boolean isAvailable() {
        return available.get();
    }
    
    public boolean setAvailable(boolean value) {
        return this.available.getAndSet(value);
    }
    
    public boolean disable() {
        if (isAvailable()) {
            if (logger.isDebugEnabled()) {
                logger.debug(getUrl() + " change status from 'online' to 'offline'!");
            }
        }
        return setAvailable(false);
    }
    
    public boolean enable() {
        if (!isAvailable()) {
            if (logger.isDebugEnabled()) {
                logger.debug(getUrl() + " change status from 'offline' to 'online'!");
            }
        }
        return setAvailable(true);
    }
    
    public int getWeight() {
        return this.weight;
    }
    
    public void setWeight(int value) {
        this.weight = value;
    }
    
    public long getLastTimeMillis() {
        return this.lastTimeMillis;
    }
    
    public void setLastTimeMillis(long lastTimeMillis) {
        this.lastTimeMillis = lastTimeMillis;
        enable();
    }
    
    @Override
    public String toString() {
        return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE).reflectionToString(this);
    }
    
}
